package bsr.basara.autodata.service.impl;

import bsr.basara.autodata.dao.ColumnConfigRepository;
import bsr.basara.autodata.dao.ColumnInfoRepository;
import bsr.basara.autodata.dao.TableConfigRepository;
import bsr.basara.autodata.dao.TableInfoRepository;
import bsr.basara.autodata.entity.ColumnConfig;
import bsr.basara.autodata.entity.ColumnInfo;
import bsr.basara.autodata.entity.TableConfig;
import bsr.basara.autodata.entity.TableInfo;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.WeightRandom;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 自动数据配置服务
 *
 * @author basara
 */
@Service
public class AutoConfigService {

    @Resource
    private TableInfoRepository tableInfoRepository;

    @Resource
    private ColumnInfoRepository columnInfoRepository;

    @Resource
    private TableConfigRepository tableConfigRepository;

    @Resource
    private ColumnConfigRepository columnConfigRepository;

    @PersistenceContext
    private EntityManager entityManager;

    private static final Pattern NAME_NUM = Pattern.compile("(\\w+)(\\d+)");

    private static final Double MAX = 100.0;

    private static final List<WeightRandom.WeightObj<String>> REFER_LIST = new ArrayList<>();

    static {
        REFER_LIST.add(new WeightRandom.WeightObj<>("circle", 0.5));
        REFER_LIST.add(new WeightRandom.WeightObj<>("sine", 0.5));
        REFER_LIST.add(new WeightRandom.WeightObj<>("bounce", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("back", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("elastic", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("circular", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("exponential", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("quintic", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("quartic", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("cubic", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("quadratic", 0.1));
        REFER_LIST.add(new WeightRandom.WeightObj<>("linear", 0.1));
    }

    /**
     * 自动配置数据生成配置
     *
     * @param tableInfo 设备表
     */
    public void autoConfig(TableInfo tableInfo) {

        TableConfig tableConfig = new TableConfig();
        tableConfig.setCreateTime(new Date());

        Integer type = tableInfo.getType();
        if (type == 1) {
            //设备表
            tableConfig.setDevTable(tableInfo.getName());
            tableConfig.setDataTable(tableInfo.getName());
            tableConfig.setDataTableId(tableInfo.getId());
            tableConfig.setTimeSpace(11);
        } else if (type == 2) {
            //数据表
            TableInfo devTable = tableInfoRepository.findById(tableInfo.getDevTableId()).orElse(new TableInfo());
            tableConfig.setDevTable(devTable.getName());
            tableConfig.setDataTable(tableInfo.getName());
            tableConfig.setDataTableId(tableInfo.getId());
            tableConfig.setTimeSpace(15);

        }
        TableConfig config = tableConfigRepository.findByDevTableAndDataTable(tableConfig.getDevTable(), tableConfig.getDataTable());
        if (config != null) {
            List<ColumnConfig> configs = columnConfigRepository.findAllByTableConfigId(config.getId());
            columnConfigRepository.deleteAll(configs);
            tableConfigRepository.delete(config);
        }
        tableConfigRepository.save(tableConfig);
        autoConfigColumn(tableConfig);
    }

    /**
     * 列定义
     */
    private void autoConfigColumn(TableConfig tableConfig) {
        List<ColumnConfig> columns = new ArrayList<>();

        List<ColumnInfo> columnInfos = columnInfoRepository.findByTypeAndTableId(1, tableConfig.getDataTableId());

        Map<String, List<ColumnInfo>> collect = columnInfos.stream().collect(Collectors.groupingBy(s -> {
            Matcher matcher = NAME_NUM.matcher(s.getName());
            if (matcher.matches()) {
                return matcher.group(1);
            }
            return "_normal";
        }));

        for (Map.Entry<String, List<ColumnInfo>> entry : collect.entrySet()) {
            String key = entry.getKey();
            List<ColumnInfo> value = entry.getValue();

            if (!"_normal".equals(key) && value.size() > 15) {
                ColumnConfig columnConfig = new ColumnConfig();
                columnConfig.setTableConfigId(tableConfig.getId());
                columnConfig.setColumn(key + value.size());
                columnConfig.setDataType(value.get(0).getDataType());
                finishConfig(columnConfig, tableConfig.getDataTable().equals(tableConfig.getDevTable()));
                columns.add(columnConfig);
            } else {
                for (ColumnInfo s : value) {
                    ColumnConfig columnConfig = new ColumnConfig();
                    columnConfig.setTableConfigId(tableConfig.getId());
                    columnConfig.setColumn(s.getName());
                    columnConfig.setDataType(s.getDataType());
                    finishConfig(columnConfig, tableConfig.getDataTable().equals(tableConfig.getDevTable()));
                    columns.add(columnConfig);
                }
            }
        }

        List<ColumnConfig> configs = columnConfigRepository.findAllByTableConfigId(tableConfig.getId());
        columnConfigRepository.deleteAll(configs);

        columnConfigRepository.saveAll(columns);
    }

    /**
     * 自动生成历史数据某字段配置
     */
    private void finishConfig(ColumnConfig config, boolean isDevTable) {
        //0固定值，1基数波动，2上下限值，3一定趋势，4其他值计算，5分片生成
        int type = RandomUtil.randomInt(6);
        if (isDevTable) {
            if ("src_id".equals(config.getColumn())) {
                type = 5;
            } else {
                type = RandomUtil.randomInt(5);
            }
        }
        config.setType(type);
        switch (type) {
            case 0:
                config.setBase(RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP));
                break;
            case 1:
                config.setBase(RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP));
                config.setRatio(RandomUtil.randomDouble(0.01, 0.5, 4, RoundingMode.HALF_UP));
                break;
            case 2:
                double max2 = RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP);
                config.setMax(max2);
                config.setMin(RandomUtil.randomDouble(max2, 4, RoundingMode.HALF_UP));
                break;
            case 3:
                WeightRandom<String> weightRandom = RandomUtil.weightRandom(REFER_LIST);
                String refer = weightRandom.next();
                config.setRefer(refer);
                config.setParam(param(refer));
                double max3 = RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP);
                config.setMax(max3);
                config.setMin(RandomUtil.randomDouble(max3, 4, RoundingMode.HALF_UP));
                break;
            case 4:
                config.setFormula("{{r}}");
                break;
            case 5:
                if (!isDevTable) {
                    config.setSharding(shardingConfig().toJSONString());
                }
                break;
            default:
                break;
        }

    }

    /**
     * 分片配置
     *
     * @return
     */
    private static JSONArray shardingConfig() {
        JSONArray shards = new JSONArray();
        Date date = new Date();
        Date begin = DateUtil.beginOfDay(date);
        Date end = DateUtil.endOfDay(date);

        //当前最新点
        double last = 0.0;

        while (true) {
            JSONObject shard = new JSONObject();
            shard.put("begin", DateUtil.format(begin, "HH:mm"));
            begin = DateUtil.offsetMinute(begin, RandomUtil.randomInt(15, 1425));
            shard.put("end", DateUtil.format(begin, "HH:mm"));
            //0固定值，1基数波动，2上下限值，3一定趋势，4其他值计算
            int type = RandomUtil.randomInt(5);
            shard.put("type", type);
            switch (type) {
                case 0:
                    shard.put("base", RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP));
                    last = shard.getDouble("base");
                    break;
                case 1:
                    shard.put("base", RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP));
                    shard.put("ratio", RandomUtil.randomDouble(0.01, 0.5, 4, RoundingMode.HALF_UP));
                    last = shard.getDouble("base");
                    break;
                case 2:
                    double max2 = RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP);
                    shard.put("max", max2);
                    shard.put("min", RandomUtil.randomDouble(max2, 4, RoundingMode.HALF_UP));
                    last = (shard.getDouble("max") + shard.getDouble("min")) / 2;
                    break;
                case 3:
                    WeightRandom<String> weightRandom = RandomUtil.weightRandom(REFER_LIST);
                    String refer = weightRandom.next();
                    shard.put("refer", refer);
                    String param = param(refer);
                    shard.put("param", param);
                    //曲线趋势
                    int upDown = upDown(refer, param);

                    double max3 = RandomUtil.randomDouble(MAX, 4, RoundingMode.HALF_UP);
                    double min3 = RandomUtil.randomDouble(max3, 4, RoundingMode.HALF_UP);

                    if (upDown == 1) {
                        shard.put("min", last);
                        max3 = RandomUtil.randomDouble(last, MAX, 4, RoundingMode.HALF_UP);
                        shard.put("max", max3);
                        last = max3;
                    } else if (upDown == -1) {
                        if (last <= 0) {
                            last = max3;
                        }
                        shard.put("max", last);
                        min3 = RandomUtil.randomDouble(last, 4, RoundingMode.HALF_UP);
                        shard.put("min", min3);
                        last = min3;
                    } else {
                        shard.put("max", max3);
                        shard.put("min", min3);
                        last = (max3 + min3) / 2;
                    }
                    break;
                case 4:
                    shard.put("formula", "{{r}}");
                    break;
                default:
                    break;
            }
            shards.add(shard);
            if (begin.after(end) || DateUtil.offsetMinute(begin, 15).after(end)) {
                shard.put("end", DateUtil.format(end, "HH:mm"));
                break;
            }
        }
        return shards;
    }

    /**
     * 趋势曲线参数
     *
     * @param refer 曲线类型
     * @return
     */
    private static String param(String refer) {
        String param;
        if ("circle".equals(refer)) {
            int from = RandomUtil.randomInt(360);
            int to;
            if (from < 180) {
                to = RandomUtil.randomInt(from + 1, 181);
            } else {
                to = RandomUtil.randomInt(from + 1, 361);
            }
            param = from + "," + to;
        } else if ("sine".equals(refer)) {
            int i1 = RandomUtil.randomInt(4);
            int i2 = RandomUtil.randomInt(i1 + 1, 5);
            param = "1234".substring(i1, i2);
        } else if ("linear".equals(refer)) {
            param = RandomUtil.randomString("12", 1);
        } else {
            param = RandomUtil.randomString("1234", 1);
        }
        return param;
    }

    /**
     * 趋势
     *
     * @param refer 曲线类型
     * @param param 曲线参数
     * @return -1下降趋势 0持平 1上升趋势
     */
    private static int upDown(String refer, String param) {
        int sl = 0;
        if ("circle".equals(refer)) {
            String[] split = param.split(",");
            int from = Integer.parseInt(split[0]);
            int to = Integer.parseInt(split[1]);
            if (from < 180) {
                if (Math.abs(to - 90) > Math.abs(from - 90)) {
                    sl = 1;
                } else if (Math.abs(to - 90) < Math.abs(from - 90)) {
                    sl = -1;
                }
            } else {
                if (Math.abs(to - 270) > Math.abs(from - 270)) {
                    sl = 1;
                } else if (Math.abs(to - 270) < Math.abs(from - 270)) {
                    sl = -1;
                }
            }
        } else if ("sine".equals(refer)) {
            if ("1".equals(param) || "2".equals(param) || "12".equals(param) || "123".equals(param)) {
                sl = 1;
            }
            if ("3".equals(param) || "4".equals(param) || "34".equals(param) || "234".equals(param)) {
                sl = -1;
            }
        } else if ("linear".equals(refer)) {
            if ("1".equals(param)) {
                sl = 1;
            }
            if ("2".equals(param)) {
                sl = -1;
            }
        } else {
            if ("1".equals(param) || "2".equals(param)) {
                sl = 1;
            }
            if ("3".equals(param) || "4".equals(param)) {
                sl = -1;
            }
        }
        return sl;
    }

}
